package org.eclipse.gittab.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.gittab.gitTabDsl.Fret;
import org.eclipse.gittab.gitTabDsl.GuitarStr;
import org.eclipse.gittab.gitTabDsl.Note;
import org.eclipse.gittab.gitTabDsl.NoteName;
import org.eclipse.gittab.gitTabDsl.NoteWrapper;
import org.eclipse.gittab.gitTabDsl.TabModel;
import org.eclipse.gittab.gitTabDsl.Tuning;
import org.eclipse.xtext.EcoreUtil2;

public interface TuningMapper {

	public static final String[] notes = new String[] { "C", "C#/Db", "D",
			"D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab", "A", "A#/Bb", "B" };

	/**
	 * Gets the human readable notes from a tuning.
	 * 
	 * @param tuning
	 * @return
	 */
	public Map<GuitarStr, List<String>> getNotes(Tuning tuning);

	/**
	 * Gets the fret position from a note using the tuning in the contained
	 * {@link TabModel} object.
	 * 
	 * @param note
	 * @return -1 if the tuning of the note can not be obtained
	 */
	public int getFret(Note note);

	/**
	 * Gets the fret position from a note using the tuning in the contained
	 * {@link TabModel} object.
	 * 
	 * @param note
	 * @return
	 */
	public int getFret(Tuning tuning, Note note);

	public interface TypeRegistry {
		/**
		 * Gets the instance that is acting as a cache for several
		 * {@link Tuning}s.
		 */
		public static final TuningMapper CACHE = new TuningMapperCache();
	}

	public class Util {

		/**
		 * Transforms the note map from {@link TuningMapper#getNotes(Tuning)} to
		 * a note matrix where the first dimension is the guitar string and the
		 * second dimenson the notes.
		 * 
		 * @param noteMap
		 * @return
		 */
		public static String[][] mapToArray(Map<GuitarStr, List<String>> noteMap) {

			final String[][] notes = new String[noteMap.size()][];

			int i = 0;
			for (Map.Entry<GuitarStr, List<String>> entry : noteMap.entrySet()) {

				notes[i++] = entry.getValue().toArray(
						new String[entry.getValue().size()]);
			}

			return notes;
		}
	}

	class TuningMapperCache extends
			HashMap<Tuning, Map<GuitarStr, List<String>>> implements
			TuningMapper {

		/**
 * 
 */
		private static final long serialVersionUID = 6721655828443208178L;

		public TuningMapperCache() {

			super(5);
		}

		/**
		 * Adds the calculated notes to the cache.
		 */
		@Override
		public Map<GuitarStr, List<String>> getNotes(Tuning tuning) {

			if (!containsKey(tuning)) {

				Map<GuitarStr, List<String>> noteMap = new LinkedHashMap<GuitarStr, List<String>>(
						tuning.getStrings().size());

				int frets = tuning.getFrets() + 1;

				for (GuitarStr string : tuning.getStrings()) {

					List<String> noteList = new ArrayList<String>(frets);

					String noteName = string.getNote().getName();

					int startIdx = searchNoteIdx(noteName, notes);

					for (int f = 0; f < frets; f++) {

						noteList.add(notes[(startIdx + f) % notes.length]);
					}

					noteMap.put(string, noteList);
				}

				put(tuning, noteMap);
			}

			return get(tuning);
		}

		/**
		 * If the tuning from the note is not yet available the notes from the
		 * tuning is added to the cache.
		 */
		@Override
		public int getFret(Note note) {

			final TabModel tabmodel = EcoreUtil2.getContainerOfType(note,
					TabModel.class);

			if (tabmodel == null) {
				return -1;
			}

			return getFret(tabmodel.getTuning(), note);
		}

		@Override
		public int getFret(Tuning tuning, Note note) {

			getNotes(tuning);

			NoteWrapper noteWrapper = note.getNote();

			if (noteWrapper instanceof Fret) {

				Fret fret = (Fret) noteWrapper;

				return fret.getFret();
			} else /* if (noteWrapper instanceof NoteName) */{

				int fret = 0;

				NoteName noteName = (NoteName) noteWrapper;

				fret = searchNoteIdx(noteName.getNote().getName(), get(tuning)
						.get(note.getGitString()).toArray(new String[0]));

				if (noteName.isOctave()) {
					// add an octave (12 frets) and get the rest of the
					// total
					// amount
					// of frets
					fret = (fret + 12) % tuning.getFrets();
				}

				return fret;
			}
		}

		protected int searchNoteIdx(String noteName, String[] notesToSearch) {

			boolean isNaturalNote = (!noteName.contains("#") || !noteName
					.contains("b"));

			for (int i = 0; i < notesToSearch.length; i++) {

				String note = notesToSearch[i];
				if (isNaturalNote) {
					if (!note.contains("/") && note.equals(noteName)) {
						return i;
					}
				} else {
					if (note.contains(noteName)) {
						return i;
					}
				}
			}

			return 0;
		}

	}

}
